﻿/*
  Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL/
  
  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the
  License.
  
  The Original Code is [maashaack framework].
  
  The Initial Developers of the Original Code are
  Zwetan Kjukov <zwetan@gmail.com> and Marc Alcaraz <ekameleon@gmail.com>.
  Portions created by the Initial Developers are Copyright (C) 2006-2014
  the Initial Developers. All Rights Reserved.
  
  Contributor(s):
  
  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the LGPL or the GPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.
*/
package molecule.render.starling.display
{
    import core.maths.clamp;

    import graphics.Align;
    import graphics.Drawable;
    import graphics.transitions.FrameTimer;

    import molecule.Layout;

    import starling.display.DisplayObjectContainer;
    import starling.display.Sprite;
    import starling.events.Event;

    import system.hack;
    import system.process.Lockable;
    import system.signals.Signal;
    import system.signals.Signaler;

    import flash.geom.Rectangle;
    
    use namespace hack ;
    
    /**
     * The Movable Object Block (MOB) defines an advanced Sprite object.
     */
    public class MOB extends Sprite implements Drawable, Lockable
    {
        /**
         * Creates a new MOB instance.
         * @param init An object that contains properties with which to populate the newly instance. If init is not an object, it is ignored.
         * @param locked The flag to lock the new current object when is created.
         */
        public function MOB( init:Object = null , locked:Boolean = false )
        {
            addEventListener( Event.ADDED_TO_STAGE , _addedToStage ) ;
            
            ///////////
            
            if( !_scope )
            {
                _scope = this ;
            }
            
            ///////////
            
            if( locked )
            {
                lock() ;
            }
            
            initialize( init ) ;
            
            if( locked )
            {
                unlock() ;
            }
            
            ///////////
        }
        
        /**
         * The alignement of the background.
         * @see graphics.Align
         */
        public function get align():uint
        {
            return _align ;
        }
        
        /**
         * @private
         */
        public function set align( value:uint ):void
        {
            _align = value ;
            if ( _locked == 0 ) 
            {
                update() ;
            }
        }
        
        /**
         * This signal emit when the sprite is changed.
         */
        public function get changed():Signaler
        {
            return _changed ;
        }
        
        /**
         * Indicates the enabled state of the object.
         */
        public function get enabled():Boolean 
        {
            return _enabled ;
        }
        
        /**
         * @private
         */
        public function set enabled( value:Boolean ):void 
        {
            _enabled = value ;
            if ( _locked == 0 ) 
            {
                viewEnabled() ;
            }
        }
        
        /**
         * Determinates the virtual height value of this component.
         */
        public function get h():Number 
        {
            return clamp( _h , _minHeight, _maxHeight) ;
        }
        
        /**
         * @private
         */
        public function set h( value:Number ):void 
        {
            _h = clamp( value , _minHeight, _maxHeight ) ;
            if ( _locked == 0 ) 
            {
                update() ;
            }
            notifyResized() ;
        }
        
        /**
         * Determinates the layout of this container.
         */
        public function get layout():Layout
        {
            return _layout ;
        }
        
        /**
         * @private
         */
        public function set layout( layout:Layout ):void
        {
            if ( _layout )
            {
                _layout.unlock() ;
                _layout.renderer.disconnect( renderLayout ) ;
                _layout.updater.disconnect( updateLayout ) ;
            }
            _layout = layout ;
            if ( _layout )
            {
                _layout.renderer.connect( renderLayout ) ;
                _layout.updater.connect( updateLayout ) ;
                _layout.container = _scope ;
                if ( isLocked() )
                {
                    _layout.lock() ;
                }
                else
                {
                    _layout.unlock() ;
                }
            }
            if ( _locked == 0 ) 
            {
                update() ;
            }
        }
        
        /**
         * This property defined the maximum height of this display.
         */
        public function get maxHeight():Number
        {
            return _maxHeight ;
        }
        
        /**
         * @private
         */
        public function set maxHeight( value:Number ):void
        {
            _maxHeight = value ;
            if ( _maxHeight < _minHeight )
            {
                _maxHeight = _minHeight ;
            }
            if ( _locked == 0 ) 
            {
                update() ;
            }
        }
        
        /**
         * Defines the maximum width of this display.
         */
        public function get maxWidth():Number
        {
            return _maxWidth ;
        }
        
        /**
         * @private
         */
        public function set maxWidth( value:Number ):void
        {
            _maxWidth = value ;
            if ( _maxWidth < _minWidth )
            {
                _maxWidth = _minWidth ;
            }
            if ( _locked == 0 ) 
            {
                update() ;
            }
        }
        
        /**
         * This property defined the mimimun height of this display (This value is >= 0).
         */
        public function get minHeight():Number
        {
            return _minHeight ;
        }
        
        /**
         * @private
         */
        public function set minHeight( value:Number ):void
        {
            _minHeight = value > 0 ? value : 0 ;
            if ( _minHeight > _maxHeight )
            {
                _minHeight = _maxHeight ;
            }
            if ( _locked == 0 ) 
            {
                update() ;
            }
        }
        
        /**
         * This property defined the mimimun width of this display (This value is >= 0).
         */
        public function get minWidth():Number
        {
            return _minWidth ;
        }
        
        /**
         * @private
         */
        public function set minWidth( value:Number ):void
        {
            _minWidth = value > 0 ? value : 0 ;
            if ( _minWidth > _maxWidth )
            {
                _minWidth = _maxWidth ;
            }
            if ( _locked == 0 ) 
            {
                update() ;
            }
        }
        
        /**
         * This signal emit before the rendering is started.
         */
        public function get renderer():Signaler
        {
            return _renderer ;
        }
        
        /**
         * This signal emit when the sprite is resized.
         */
        public function get resized():Signaler
        {
            return _resized ;
        }
        
        /**
         * Determinates the scope of the container. 
         * By default the scope is the container itself but can target any other DisplayObject reference.
         */
        public function get scope():DisplayObjectContainer
        {
            return _scope ;
        }
        
        /**
         * @private
         */
        public function set scope( scope:DisplayObjectContainer ):void
        {
            if( scope )
            {
                _scope = scope ;
            }
            else
            {
                _scope = this ;
            }
            if ( _layout )
            {
                _layout.container = _scope ;
            }
        }
        
        /**
         * This signal emit after the rendering is finished.
         */
        public function get updater():Signaler
        {
            return _updater ;
        }
        
        /**
         * Determinates the virtual height value of this component.
         */
        public function get w():Number 
        {
            return clamp( _w , _minWidth, _maxWidth ) ;
        }
        
        /**
         * @private
         */
        public function set w( value:Number ):void 
        {
            _w = clamp( value , _minWidth, _maxWidth ) ;
            if ( _locked == 0 ) 
            {
                update() ;
            }
            notifyResized() ;
        }
        
        /**
         * Launch an event with a delayed interval.
         */
        public function doLater():void 
        {
            if ( isLocked() ) 
            {
                return ;
            }
            if( ___timer___.timer.connected() )
            {
                ___timer___.timer.disconnect( redraw ) ;
            }
            ___timer___.timer.connect( redraw , 0 , true ) ;
            ___timer___.start() ;
        }
        
        /**
         * Draw the display.
         */
        public function draw( ...args:Array ):void
        {
            //
        }
        
        /**
         * Initialize the canvas.
         * @param init An object that contains properties with which to populate the newly instance. If init is not an object, it is ignored.
         */
        public function initialize( init:Object = null ):void 
        {
            if( init )
            {
                lock() ;
                for( var prop:String in init )
                {
                    this[prop] = init[prop] ;
                }
                unlock() ;
            }
            if ( _locked == 0 ) 
            {
                update() ;
            }
        }
        
        /**
         * Indicates if the object is locked.
         */
        public function isLocked():Boolean
        {
            return _locked > 0 ;
        }
        
        /**
         * Locks the object.
         */
        public function lock():void
        {
            _locked ++ ;
            if ( _layout )
            {
                _layout.lock() ;
            }
        }
        
        /**
         * Moves the canvas object..
         * @param x The x position of the component.
         * @param y The y position of the component.
         */
        public function move( x:Number=NaN , y:Number=NaN ):void
        {
            if ( !isNaN(x) )
            {
                this.x = x ;
            }
            if ( !isNaN(y) )
            {
                this.y = y ;
            }
        }
        
        /**
         * Notify an event when you resize the component.
         */
        public function notifyResized():void 
        {
            viewResize() ;
            _resized.emit( this ) ;
        }
        
        /**
         * Reset the lock security of the object.
         */
        public function resetLock():void 
        {
            _locked = 0 ;
        }
        
        /**
         * Unlocks the display.
         */
        public function unlock():void
        {
            _locked = (--_locked > 0 ) ? _locked : 0 ;
            if ( _layout )
            {
                _layout.unlock() ;
            }
        }
        
        
        /**
         * Update the display.
         */
        public function update():void 
        {
            if ( _locked > 0 ) 
            {
                return ;
            }
            
            _renderer.emit(this) ;
            
            if ( _layout )
            {
                _layout.run() ;
            }
            
            draw() ;
            
            viewChanged() ;
            
            altered = false ;
            
            _updater.emit(this) ;
        }
        
        /**
         * Indicates if the display is altered and must be invalidate.
         * @private
         */
        protected var altered:Boolean ;
        
        /**
         * @private
         */
        protected const _changed:Signaler = new Signal() ;
        
        /**
         * @private
         */
        protected const _renderer:Signaler = new Signal() ;
        
        /**
         * @private
         */
        protected const _resized:Signaler = new Signal() ;
        
        /**
         * @private
         */
        protected const _updater:Signaler = new Signal() ;
        
        /**
         * Invoked when the sprite is added to the stage.
         */
        protected function addedToStage( e:Event = null ):void
        {
            // override
        }
        
        /**
         * Defines a change in the sprite and invalidate the stage rendering. 
         */
        protected function change():void
        {
            altered = true;
        }
        
        /**
         * Redraws the sprite.
         */
        protected function redraw():void 
        {
            if( ___timer___.running )
            {
                ___timer___.stop() ;
            }
            update() ;
        }
        
        /**
         * Invoked when the sprite is removed from the stage.
         */
        protected function removedFromStage( e:Event = null ):void
        {
            // override
        }
        
        /**
         * Receives a message when the layout emit when is rendered.
         */
        protected function renderLayout( layout:Layout = null ):void
        {
            //
        }
        
        /**
         * Invoked when the stage is rendering.
         */
        protected function renderStage( e:Event = null ):void
        {
            if ( altered ) 
            {
                redraw() ;
            }
        }
        
        /**
         * Sets the preferred width (w) and height (h) values of the display.
         */
        public function setPreferredSize( w:Number, h:Number ):void
        {
            _w = isNaN(w) ? 0 : clamp( w , _minWidth, _maxWidth) ; 
            _h = isNaN(h) ? 0 : clamp( h , _minHeight, _maxHeight) ;
            if ( _locked == 0 ) 
            {
                update() ;
            }
            notifyResized() ;
        }
        
        /**
         * Sets the width and height values of the display.
         */
        public function setSize( w:Number, h:Number ):void
        {
            width  = isNaN(w) ? 0 : clamp( w , _minWidth, _maxWidth) ; 
            height = isNaN(h) ? 0 : clamp( h , _minHeight, _maxHeight) ;
            if ( _locked == 0 ) 
            {
                update() ;
            }
            notifyResized() ;
        }
        
        /**
         * Receives a message when the layout emit when is updated.
         */
        protected function updateLayout( layout:Layout = null ):void
        {
            //
        }
        
        //////////
        
        /**
         * This method is invoked after the draw() method in the update() method.
         * Overrides this method.
         */
        protected function viewChanged():void
        {
            // overrides
        }
        
        /**
         * Invoked when the enabled property of the component change.
         * Overrides this method.
         */
        protected function viewEnabled():void 
        {
            // overrides
        }
        
        /**
         * Invoked when the component is resized.
         * Overrides this method.
         */
        protected function viewResize():void 
        {
            // overrides
        }
        
        /////////////
        
        /**
         * Invoked when the sprite is added to the stage.
         * @private
         */
        protected function _addedToStage( e:Event = null ):void
        {
            removeEventListener( Event.ADDED_TO_STAGE , _addedToStage ) ;
            addEventListener( Event.REMOVED_FROM_STAGE , _removedFromStage ) ;
            
            addedToStage( e ) ;
        }
        
        /**
         * Invoked when the sprite is removed from the stage.
         * @private
         */
        protected function _removedFromStage( e:Event = null ):void
        {
            addEventListener( Event.ADDED_TO_STAGE , _addedToStage ) ;
            removeEventListener( Event.REMOVED_FROM_STAGE , _removedFromStage ) ;
            
            removedFromStage( e ) ;
        }
        
        /////////////
        
        /**
         * @private
         */
        hack var _align:uint = 10 ; // top left
        
        /**
         * @private
         */
        hack var _enabled:Boolean = true ; // FIXME
        
        /**
         * @private
         */
        hack var _h:Number = 0 ;
        
        /**
         * @private
         */
        hack var _layout:Layout ;
        
        /**
         * @private
         */ 
        hack var _locked:uint ;
        
        /**
         * @private
         */
        hack var _maxHeight:Number ;
        
        /**
         * @private
         */
        hack var _maxWidth:Number ;
        
        /**
         * @private
         */
        hack var _minHeight:Number = 0 ;
        
        /**
         * @private
         */
        hack var _minWidth:Number = 0 ;
        
        /**
         * @private
         */
        hack const _real:Rectangle = new Rectangle();
        
        /**
         * The scope of the active display list of this container.
         * @private
         */
        hack var _scope:DisplayObjectContainer ;
        
        /**
         * @private
         */
        hack var _w:Number = 0 ;
        
        /**
         * Refresh the real area Rectangle of the background with the current alignement.
         */
        hack function fixArea():Rectangle
        {
            // initialize
            
            _real.width  = w ;
            _real.height = h ;
            _real.x      = 0 ;
            _real.y      = 0 ;
            
            // update
            
            if( _align == Align.BOTTOM ) 
            {
                _real.x -= _real.width / 2 ;
                _real.y -= _real.height ;
            }
            else if ( _align == Align.BOTTOM_LEFT )
            {
                _real.y -= _real.height ;
            }
            else if ( _align == Align.BOTTOM_RIGHT )
            {
                _real.x -= _real.width ;
                _real.y -= _real.height ;
            }
            else if ( _align == Align.CENTER )
            {
                _real.x -= _real.width / 2 ;
                _real.y -= _real.height / 2 ;
            }
            else if ( _align == Align.LEFT )
            {
                _real.y -= _real.height / 2 ;
            }
            else if ( _align == Align.RIGHT )
            {
                _real.x -= _real.width ;
                _real.y -= _real.height / 2 ;
            }
            else if ( _align == Align.TOP )
            {
                _real.x -= _real.width / 2 ;
            }
            else if ( _align == Align.TOP_RIGHT )
            {
                _real.x -= _real.width ;
            }
            
            // result
            
            return _real ;
        }
        
        /////////////
        
        /**
         * @private
         */
        private const ___timer___:FrameTimer = new FrameTimer(24, 1) ;
    }
}